package utility;



public class Matrix {
	
	public double[][] elements;

	private static final int POS = 1;
	private static final int NEG = -1;

	/*
	 * Construct a new matrix with given data.
	 * 
	 * @param elements Non-null 2D data array in which every row has the same
	 *   number of columns.
	 */
	public Matrix(double[][] elements) {
		if(elements == null) {
			throw new IllegalArgumentException("Matrix cannot be null.");
		} else if(elements.length == 0) {
			throw new IllegalArgumentException("Matrix cannot be empty.");
		} else {
			int columns = elements[0].length;
			for(int i = 1; i < elements.length; i++) {
				if(elements[i].length != columns || columns == 0) {
					throw new IllegalArgumentException("All rows must have " +
							"the same number of columns in a matrix.");
				}
			}
		}
		this.elements = elements.clone();
	}
	
	/*
	 * Multiplies this Matrix and other Matrix.
	 * 
	 * @param other The Matrix to multiply with. This Matrix's columns
	 *   should be the same length as the other Matrix's rows.
	 * @return The product of this x other
	 */
	public Matrix multiply(Matrix other) {
		double[][] otherElements = other.elements;
		double[][] product;
		if(elements[0].length != otherElements.length) {
			throw new IllegalArgumentException("The number of columns of " +
					"matrix A must equal the number of rows of matrix B " +
					"to multiply.");
		}
		int rows = elements.length;
		int columns = otherElements[0].length;
		product = new double[rows][columns];
		for(int i = 0; i < rows; i++) {
			for(int j = 0; j < columns; j++) {
				for(int k = 0; k < elements[i].length; k++) {
					product[i][j] += elements[i][k] * otherElements[k][j];
				}
			}
		}
		return new Matrix(product);
	}
	
	/*
	 * Multiples this Matrix and Vector vector.
	 *   |        |   | v |   | v |
	 *   | matrix | x | e | = | e |
	 *   |        |   | c |   | c |
	 */
	public Vector multiply(Vector vector) {
		double[] vectorElements = vector.elements;
		double[] product;
		if(elements[0].length != vectorElements.length) {
			throw new IllegalArgumentException("The number of columns of " +
					"matrix must equal the number of elements of the vector " +
					"to multiply.");
		}
		int rows = elements.length;
		product = new double[rows];
		for(int i = 0; i < rows; i++) {
			for(int k = 0; k < elements[i].length; k++) {
				product[i] += elements[i][k] * vectorElements[k];
			}
		}
		return new Vector(product);
	}
	
	/*
	 * Helper method that calls sum() to add matrices together.
	 * @return The sum of adding this Matrix and other Matrix
	 */
	public Matrix add(Matrix other) {
		return this.sum(other, POS);
	}
	
	/*
	 * Helper method that calls sum() to subtract other matrix from this matrix.
	 * @return The difference of other Matrix from this Matrix.
	 */
	public Matrix minus(Matrix other) {
		return this.sum(other, NEG);
	}
	
	/*
	 * Sums two matrices together.
	 * @param other The other Matrix (of equal dimensions to this Matrix) to sum
	 * @param sign Indicates whether other Matrix should be added or subtracted
	 * 		if(sign == POS) add Matrices
	 * 		if(sign == NEG) subtract other Matrix from this Matrix
	 */
	private Matrix sum(Matrix other, int sign) {
		double[][] otherElements = other.elements;
		double[][] sum;
		if(elements.length != otherElements.length || 
				elements[0].length != otherElements[0].length) {
			throw new IllegalArgumentException("The matrices must have the " +
					"same dimensions to subtract.");
		}
		sum = new double[elements.length][elements[0].length];
		int rows = elements.length;
		int columns = elements[0].length;
		for(int i = 0; i < rows; i++) {
			for(int j = 0; j < columns; j++) {
				sum[i][j] = elements[i][j] + (sign) * otherElements[i][j];
			}
		}
		return new Matrix(sum);
	}
	
	/*
	 * Returns the transpose of the Matrix.
	 */
	public Matrix transpose() {
		double[][] transpose = new double[elements[0].length][elements.length];
		for(int i = 0; i < elements.length; i++) {
			for(int j = 0; j < elements[i].length; j++) {
				transpose[i][j] = elements[j][i];
			}
		}
		return new Matrix(transpose);
	}
	
	/*
	 * Returns the element in the Matrix at position (i, j).
	 * @param i the row containing the desired element
	 * @param j the column containing the desired element
	 * @return the element at position (i,j)
	 */
	public double get(int i, int j) {
		return elements[i][j];
	}
	
	/*
	 * Returns Matrix as String
	 *   | a  b  c |
	 *   | d  e  f |
	 *   | g  h  i |
	 * @return String representation of Matrix where elements are tab-aligned
	 */
	public String toString() {
		String matrix = "";
		if(elements == null) {
			return "null";
		}
		for(int i = 0; i < elements.length; i++) {
			matrix += "| ";
			for(int j = 0; j < elements[0].length; j++) {
				matrix += elements[i][j] + "\t";
			}
			matrix += " |\n";
		}
		return matrix;
	}

}
